﻿//#define DEV_DB
using Circus.Utility;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Data.SqlClient;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Web;

namespace WoAiPao {
    
    /// <summary>
    /// 提供数据库和对象的互相绑定。
    /// </summary>
    public static class DbBinder {

        static DbBinder() {
            DbHelper.connectionString = getConnectionString();
        }

        /// <summary>
        /// 获取当前网站的连接字符串。
        /// </summary>
        private static string getConnectionString() {
#if DEV_DB
            const string DBName = "Dev";
#else
            const string DBName = "Default";
#endif
            var connectionSectionString = System.Configuration.ConfigurationManager.ConnectionStrings[DBName].ConnectionString;

            System.Reflection.Assembly assemby = null;
            try {
                assemby = System.Reflection.Assembly.LoadFile(HttpRuntime.BinDirectory + "/Arch.Data.dll");
                var cols = (ConnectionStringSettingsCollection)assemby.GetType("Arch.Data.DbEngine.ConnectionStringProviderDal").GetMethod("GetConnectionStrings").Invoke(null, new object[] { new string[] { connectionSectionString } });
                connectionSectionString = cols[0].ConnectionString;
            } catch { }

            try {
                connectionSectionString = (string)assemby.GetType("Arch.Data.DbEngine.ConnectionStringHelper").GetMethod("GetDecryptConnectionString").Invoke(null, new object[1] {
                        connectionSectionString
                    });
            } catch (Exception) { }

            return connectionSectionString;
        }

        public static void init() {

        }

        /// <summary>
        /// 获取一个成员对应的数据库字段名。
        /// </summary>
        /// <param name="member"></param>
        /// <returns></returns>
        private static string getDbFiledName(MemberInfo member) {
            var attr = member.GetCustomAttributes(typeof(DbBindAttribute), true);
            if (attr.Length == 0) {
                return member.Name;
            }
            return ((DbBindAttribute)attr[0]).name;
        }

        private static bool isValidField(PropertyInfo property) {

            // 属性必须可写。
            if (!property.CanWrite) {
                return false;
            }

            // 属性必须是值类型或字符串类型。
            if (property.PropertyType.IsValueType || property.PropertyType == typeof(string)) {
                return true;
            }

            return false;
        }

        /// <summary>
        /// 在指定的表插入一行数据。
        /// </summary>
        /// <param name="value">要插入的对象。</param>
        /// <returns>返回新建行的序号。</returns>
        public static long insert<T>(T value) {

#if DEV_DB

            var fields = typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public);
            var values = new List<object>(fields.Length);
            PropertyInfo idField = null;

            StringBuilder sb = new StringBuilder(32);
            sb.Append("INSERT INTO ");
            sb.Append(getDbFiledName(typeof(T)));
            sb.Append("(");
            for (int i = 0; i < fields.Length; i++) {

                // 忽略只读属性。
                if (!isValidField(fields[i])) {
                    continue;
                }

                // 忽略 ID 字段。
                if (fields[i].GetCustomAttributes(typeof(DbPrimaryKeyAttribute), true).Length > 0) {
                    idField = idField ?? fields[i];
                    continue;
                }

                // 忽略不匹配字段。
                var fieldName = getDbFiledName(fields[i]);
                if (fieldName == null) {
                    continue;
                }

                // 插入逗号。
                if (sb[sb.Length - 1] != '(') {
                    sb.Append(", ");
                }

                sb.Append("[");
                sb.Append(fieldName);
                sb.Append("]");
                values.Add(fields[i].GetValue(value, null));
            }
            sb.Append(") VALUES(");
            for (var i = 0; i < values.Count; i++) {
                if (i > 0) {
                    sb.Append(", ");
                }
                sb.Append("@");
                sb.Append(i);
            }
            sb.Append(");SELECT @@IDENTITY");

            var id = (long)(decimal)DbHelper.executeSqlScalar(sb.ToString(), values.ToArray());

            if (idField != null) {
                idField.SetValue(value, id, null);
            }

            return id;

#else

            var fields = typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public);
            var values = new List<object>(fields.Length);
            PropertyInfo idField = null;
            SqlParameter idParameter = null;
            var id = 0L;

            using (var conn = DbHelper.createConnection()) {
                var cmd = conn.createProcedure("sp3_" + getDbFiledName(typeof(T)) + "_i");

                for (int i = 0; i < fields.Length; i++) {

                    // 忽略只读属性。
                    if (!isValidField(fields[i])) {
                        continue;
                    }

                    // 忽略 ID 字段。
                    if (fields[i].GetCustomAttributes(typeof(DbPrimaryKeyAttribute), true).Length > 0) {
                        idField = idField ?? fields[i];
                        idParameter = new SqlParameter(getDbFiledName(idField), SqlDbType.BigInt, 64) {
                            Direction = ParameterDirection.Output
                        };
                        cmd.Parameters.Add(idParameter);
                        continue;
                    }

                    // 忽略不匹配字段。
                    var fieldName = getDbFiledName(fields[i]);
                    if (fieldName == null) {
                        continue;
                    }

                    if (fieldName == "DataChange_CreateTime" || fieldName == "DataChange_LastTime") {
                        continue;
                    }

                    cmd.Parameters.AddWithValue(fieldName, fields[i].GetValue(value, null));
                }

                cmd.Parameters.AddWithValue("DataChange_CreateTime", DateTime.Now);
                cmd.Parameters.AddWithValue("DataChange_LastTime", DateTime.Now);
                
                cmd.ExecuteNonQuery();

                if (idParameter != null) {
                    id = (long)idParameter.Value;
                }

            }

            if (idField != null) {
                idField.SetValue(value, id, null);
            }

            return id;

#endif

        }

        /// <summary>
        /// 更新指定行。
        /// </summary>
        /// <param name="value">要更新的键值对。</param>
        /// <param name="where">更新的条件表达式。如果无条件请传递 “1=1”。</param>
        /// <returns>受影响的行数。</returns>
        public static int update<T>(T value, string where = null, params object[] args) {
#if DEV_DB
            var fields = typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public);
            var values = new List<object>(fields.Length);
            PropertyInfo idField = null;

            if (args != null) {
                values.AddRange(args);
            }

            StringBuilder sb = new StringBuilder(32);
            sb.Append("UPDATE ");
            sb.Append(getDbFiledName(typeof(T)));
            sb.Append(" SET ");

            var shouldAppendComma = false;
            for (int i = 0; i < fields.Length; i++) {

                // 忽略只读属性。
                if (!isValidField(fields[i])) {
                    continue;
                }

                // 忽略 ID 字段。
                if (fields[i].GetCustomAttributes(typeof(DbPrimaryKeyAttribute), true).Length > 0) {
                    idField = idField ?? fields[i];
                    continue;
                }

                // 忽略不匹配字段。
                var fieldName = getDbFiledName(fields[i]);
                if (fieldName == null) {
                    continue;
                }

                // 插入逗号。
                if (shouldAppendComma) {
                    sb.Append(", ");
                } else {
                    shouldAppendComma = true;
                }

                sb.Append("[");
                sb.Append(fieldName);
                sb.Append("] = @");
                sb.Append(values.Count);
                values.Add(fields[i].GetValue(value, null));

            }
            sb.Append(" WHERE ");

            if (where != null) {
                sb.Append(where);
            } else {
                if (idField == null) {
                    throw new ArgumentException("类型" + typeof(T).FullName + "缺少主键，同时又未指定更新条件", "T");
                }

                sb.Append(getDbFiledName(idField));
                sb.Append(" = @");
                sb.Append(values.Count);
                values.Add(idField.GetValue(value, null));
            }

            return DbHelper.executeSqlNonQuery(sb.ToString(), values.ToArray());
#else
            var fields = typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public);

            if (where != null) {
                var idField = getPrimaryField<T>();
                if (idField == null) {
                    throw new ArgumentException("类型" + typeof(T).FullName + "缺少主键", "T");
                }
                int count = 0;
                foreach (var t in DbHelper.selectRows(getDbFiledName(typeof(T)), getDbFiledName(idField), where, null, args)) {
                    idField.SetValue(value, t[0], null);
                    count += update<T>(value);
                }
                return count;
            }

            using (var conn = DbHelper.createConnection()) {
                var cmd = conn.createProcedure("sp3_" + getDbFiledName(typeof(T)) + "_u");

                for (int i = 0; i < fields.Length; i++) {

                    // 忽略只读属性。
                    if (!isValidField(fields[i])) {
                        continue;
                    }

                    // 忽略不匹配字段。
                    var fieldName = getDbFiledName(fields[i]);
                    if (fieldName == null) {
                        continue;
                    }

                    cmd.Parameters.AddWithValue(fieldName, fields[i].GetValue(value, null));

                }

                cmd.Parameters.AddWithValue("DataChange_LastTime", DateTime.Now);
                var ret = cmd.Parameters.addReturnParameter(SqlDbType.Int);

                cmd.ExecuteNonQuery();

                return (int)ret.Value == 100 ? 0 : 1;

            }

#endif

        }

        /// <summary>
        /// 更新或插入字段。
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public static bool updateOrInsert<T>(T value) {
            if (update(value) > 0) {
                return false;
            }
            insert(value);
            return true;
        }

        /// <summary>
        /// 删除指定行。
        /// </summary>
        /// <param name="table">要更新的表。</param>
        /// <param name="value">要删除的对象。</param>
        /// <returns>如果删除成功返回 true；否则返回 false。</returns>
        public static int deleteAll<T>(string where, params object[] args) {
#if DEV_DB
            return DbHelper.delete(getDbFiledName(typeof(T)), where, args);
#else
            if (where == null) {
                throw new ArgumentException("删除时必须指定条件", "where");
            }
            var idField = getPrimaryField<T>();
            if (idField == null) {
                throw new ArgumentException("类型" + typeof(T).FullName + "缺少主键", "where");
            }
            int count = 0;
            foreach (var t in DbHelper.selectRows(getDbFiledName(typeof(T)), getDbFiledName(idField), where, null, args)) {
                count += delete<T>(t[0]) ? 1 : 0;
            }
            return count;
#endif
        }

        /// <summary>
        /// 删除指定行。
        /// </summary>
        /// <param name="table">要更新的表。</param>
        /// <param name="value">要删除的对象。</param>
        /// <returns>如果删除成功返回 true；否则返回 false。</returns>
        public static bool delete<T>(T value) {
#if DEV_DB
            var idField = getPrimaryField<T>();
            return DbHelper.delete(getDbFiledName(typeof(T)), getDbFiledName(idField) + " = @0", idField.GetValue(value, null)) > 0;
#else
            var idField = getPrimaryField<T>();
            return delete<T>(idField.GetValue(value, null));
#endif
        }

        /// <summary>
        /// 删除指定行。
        /// </summary>
        /// <param name="table">要更新的表。</param>
        /// <param name="id">要删除的行的序号。</param>
        /// <returns>如果删除成功返回 true；否则返回 false。</returns>
        public static bool delete<T>(object id) {
#if DEV_DB
            var idField = getPrimaryField<T>();
            return DbHelper.delete(getDbFiledName(typeof(T)), getDbFiledName(idField) + " = @0", id) > 0;
#else
            var idField = getPrimaryField<T>();
            using (var conn = DbHelper.createConnection()) {
                var cmd = conn.createProcedure("sp3_" + getDbFiledName(typeof(T)) + "_d");
                cmd.Parameters.Add(new SqlParameter(getDbFiledName(idField), id));
                cmd.ExecuteNonQuery();
                return true;
            }
#endif
        }

        private static PropertyInfo getPrimaryField<T>() {

            // 先找到主键。
            var fields = typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public);
            for (int i = 0; i < fields.Length; i++) {
                if (fields[i].GetCustomAttributes(typeof(DbPrimaryKeyAttribute), true).Length > 0) {
                    return fields[i];
                }
            }

            throw new ArgumentException("类型 " + typeof(T).FullName + " 缺少主键；类型应定义一个属性标记为 DbPrimaryKey", "T");
        }

        /// <summary>
        /// 查询指定行。
        /// </summary>
        /// <param name="output">要更新的键值对。</param>
        /// <param name="where">查询的条件表达式。如果无条件请传递 “1=1”。</param>
        /// <returns>如果查询成功返回 true；否则返回 false。</returns>
        public static bool trySelect<T>(T output, string where = null, params object[] args) {
            string fieldString;
            List<PropertyInfo> selectedFields;
            PropertyInfo idField;
            generateSelect<T>(out fieldString, out selectedFields, out idField);

            using (var reader = DbHelper.selectReader(getDbFiledName(typeof(T)), fieldString, where, null, args)) {
                if (reader.Read()) {
                    get(reader, selectedFields, output);
                    return true;
                }

                return false;
            }

        }

        /// <summary>
        /// 查询指定行。
        /// </summary>
        /// <param name="output">要更新的键值对。</param>
        /// <param name="where">查询的条件表达式。如果无条件请传递 “1=1”。</param>
        /// <returns>如果查询成功返回 true；否则返回 false。</returns>
        public static bool trySelect<T>(T output, object id) {

            string fieldString;
            List<PropertyInfo> selectedFields;
            PropertyInfo idField;
            generateSelect<T>(out fieldString, out selectedFields, out idField);

            if (idField == null) {
                throw new ArgumentException("类型" + typeof(T).FullName + "缺少主键", "T");
            }

            using (var reader = DbHelper.selectReader(getDbFiledName(typeof(T)), fieldString, getDbFiledName(idField) + "=@0", null, id)) {
                if (reader.Read()) {
                    get(reader, selectedFields, output);
                    return true;
                }

                return false;
            }

        }

        /// <summary>
        /// 查询指定行。
        /// </summary>
        /// <param name="output">要更新的键值对。</param>
        /// <param name="where">查询的条件表达式。如果无条件请传递 “1=1”。</param>
        /// <returns>如果查询成功返回 true；否则返回 false。</returns>
        public static bool trySelect<T>(T output) {
            string fieldString;
            List<PropertyInfo> selectedFields;
            PropertyInfo idField;
            generateSelect<T>(out fieldString, out selectedFields, out idField);

            if (idField == null) {
                throw new ArgumentException("类型" + typeof(T).FullName + "缺少主键", "T");
            }

            using (var reader = DbHelper.selectReader(getDbFiledName(typeof(T)), fieldString, getDbFiledName(idField) + "=@0", null, idField.GetValue(output, null))) {
                if (reader.Read()) {
                    get(reader, selectedFields, output);
                    return true;
                }

                return false;
            }

        }

        private static void generateSelect<T>(out string fieldString, out List<PropertyInfo> selectedFields, out PropertyInfo idField) {

            selectedFields = new List<PropertyInfo>();
            idField = null;

            StringBuilder sb = new StringBuilder(32);

            var fields = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
            for (int i = 0; i < fields.Length; i++) {

                // 忽略只读属性。
                if (!isValidField(fields[i])) {
                    continue;
                }

                // 保存 ID 字段。
                if (fields[i].GetCustomAttributes(typeof(DbPrimaryKeyAttribute), true).Length > 0) {
                    idField = idField ?? fields[i];
                }

                // 忽略不匹配字段。
                var fieldName = getDbFiledName(fields[i]);
                if (fieldName == null) {
                    continue;
                }

                // 插入逗号。
                if (sb.Length > 0) {
                    sb.Append(", ");
                }

                sb.Append("[");
                sb.Append(fieldName);
                sb.Append("]");
                selectedFields.Add(fields[i]);
            }

            fieldString = sb.ToString();

        }

        /// <summary>
        /// 查询指定行。
        /// </summary>
        /// <param name="where">查询的条件表达式。如果无条件请传递 “1=1”。</param>
        /// <param name="args">SQL 语句中使用的参数值，对应 SQL 语句中 @0 格式的参数。</param>
        /// <returns>如果查询成功返回 true；否则返回 false。</returns>
        public static T select<T>(string where = null, params object[] args)
            where T : new() {
            T result = new T();
            return trySelect(result, where, args) ? result : default(T);
        }

        /// <summary>
        /// 查询指定行。
        /// </summary>
        /// <param name="id">查询的主键。</param>
        /// <returns>如果查询成功返回对象，否则返回 null。</returns>
        public static T select<T>(object id)
            where T : new() {
            T result = new T();
            return trySelect(result, id) ? result : default(T);
        }

        /// <summary>
        /// 查询指定行。
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="where">查询的条件表达式。如果无条件请传递 null。</param>
        /// <param name="orderBy">排序使用的字段。如果不排序请传递 null。如果需要倒序，在字段名后追加“DESC”。</param>
        /// <param name="args">SQL 语句中使用的参数值，对应 SQL 语句中 @0 格式的参数。</param>
        /// <returns>一个用于遍历所有行数据的迭代器。</returns>
        public static IEnumerable<T> selectAll<T>(string where = null, string orderBy = null, params object[] args)
            where T : new() {
            string fieldString;
            List<PropertyInfo> selectedFields;
            PropertyInfo idField;
            generateSelect<T>(out fieldString, out selectedFields, out idField);

            return new RowEnumerator<T>(DbHelper.selectReader(getDbFiledName(typeof(T)), fieldString, where, orderBy, args), selectedFields);
        }

        /// <summary>
        /// 查询指定行。
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="pageNumber">当前页码。页码从 1 开始。</param>
        /// <param name="pageSize">每页显示的条数。</param>
        /// <param name="where">查询的条件表达式。如果无条件请传递 null。</param>
        /// <param name="orderBy">排序使用的字段。如果不排序请传递 null。如果需要倒序，在字段名后追加“DESC”。</param>
        /// <param name="args">SQL 语句中使用的参数值，对应 SQL 语句中 @0 格式的参数。</param>
        /// <returns>一个用于遍历所有行数据的迭代器。</returns>
        public static IEnumerable<T> selectAll<T>(int pageNumber, int pageSize, string where = null, string orderBy = null, params object[] args)
            where T : new() {
            string fieldString;
            List<PropertyInfo> selectedFields;
            PropertyInfo idField;
            generateSelect<T>(out fieldString, out selectedFields, out idField);

            return new RowEnumerator<T>(DbHelper.selectReader(getDbFiledName(typeof(T)), pageNumber, pageSize, fieldString, where, orderBy ?? (idField == null ? "1" : getDbFiledName(idField)), args), selectedFields);
        }

        private struct RowEnumerator<T> : IEnumerator<T>, IEnumerable<T>
             where T : new() {

            private readonly SqlDataReader _reader;

            private readonly List<PropertyInfo> _fields;

            public RowEnumerator(SqlDataReader reader, List<PropertyInfo> fields) {
                _reader = reader;
                _fields = fields;
            }

            /// <summary>
            /// 获取集合中位于枚举数当前位置的元素。
            /// </summary>
            /// <returns>
            /// 集合中位于枚举数当前位置的元素。
            /// </returns>
            public T Current {
                get {
                    T result = new T();
                    get(_reader, _fields, result);
                    return result;
                }
            }

            /// <summary>
            /// 执行与释放或重置非托管资源相关的应用程序定义的任务。
            /// </summary>
            /// <filterpriority>2</filterpriority>
            public void Dispose() {
                _reader.Dispose();
            }

            /// <summary>
            /// 获取集合中的当前元素。
            /// </summary>
            /// <returns>
            /// 集合中的当前元素。
            /// </returns>
            /// <filterpriority>2</filterpriority>
            object System.Collections.IEnumerator.Current {
                get {
                    return Current;
                }
            }

            /// <summary>
            /// 将枚举数推进到集合的下一个元素。
            /// </summary>
            /// <returns>
            /// 如果枚举数成功地推进到下一个元素，则为 true；如果枚举数越过集合的结尾，则为 false。
            /// </returns>
            /// <exception cref="T:System.InvalidOperationException">在创建了枚举数后集合被修改了。</exception><filterpriority>2</filterpriority>
            public bool MoveNext() {
                return _reader.Read();
            }

            /// <summary>
            /// 将枚举数设置为其初始位置，该位置位于集合中第一个元素之前。
            /// </summary>
            /// <exception cref="T:System.InvalidOperationException">在创建了枚举数后集合被修改了。</exception><filterpriority>2</filterpriority>
            public void Reset() {
                throw new InvalidOperationException("无法重置 SqlDataReader，请重新查询");
            }

            /// <summary>
            /// 返回一个循环访问集合的枚举器。
            /// </summary>
            /// <returns>
            /// 可用于循环访问集合的 <see cref="T:System.Collections.Generic.IEnumerator`1"/>。
            /// </returns>
            /// <filterpriority>1</filterpriority>
            public IEnumerator<T> GetEnumerator() {
                return this;
            }

            /// <summary>
            /// 返回一个循环访问集合的枚举器。
            /// </summary>
            /// <returns>
            /// 可用于循环访问集合的 <see cref="T:System.Collections.IEnumerator"/> 对象。
            /// </returns>
            /// <filterpriority>2</filterpriority>
            System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
                return this;
            }
        }

        private static void get<T>(SqlDataReader reader, List<PropertyInfo> selectedFields, T output) {
            for (int i = 0; i < selectedFields.Count; i++) {
                selectedFields[i].SetValue(output, reader[i], null);
            }
        }

        /// <summary>
        /// 读取当前数据读取器并填充相关数据到指定对象。
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="reader">当前读取器。</param>
        /// <param name="output">要输出的对象。</param>
        public static void get<T>(this SqlDataReader reader, T output) {

            PropertyInfo[] fields = null;

            for (var i = 0; i < reader.FieldCount; i++) {
                var name = reader.GetName(i);

                // 先尝试获取同名属性。
                var prop = typeof(T).GetProperty(name, BindingFlags.Public | BindingFlags.Instance);
                if (prop == null || getDbFiledName(prop) != name) {

                    if (fields == null) {
                        fields = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
                    }

                    // 搜索所有路径中名字匹配的字段。
                    for (int j = 0; j < fields.Length; j++) {
                        if (getDbFiledName(fields[j]) == name) {
                            prop = fields[j];
                            break;
                        }
                    }
                }

                if (prop != null && isValidField(prop)) {
                    prop.SetValue(output, reader.GetValue(i), null);
                }
            }
        }

        /// <summary>
        /// 读取当前数据读取器并返回完整对象。
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="reader">当前读取器。</param>
        public static T get<T>(this SqlDataReader reader)
            where T : new() {
            T result = new T();
            get(reader, result);
            return result;
        }

        /// <summary>
        /// 统计指定表的行数。
        /// </summary>
        /// <param name="where">查询的条件表达式。如果无条件请传递 null。</param>
        /// <param name="args">SQL 语句中使用的参数值，对应 SQL 语句中 @0 格式的参数。</param>
        /// <returns>返回行数。</returns>
        public static int count<T>(string where = null, params object[] args) {
            return DbHelper.count(getDbFiledName(typeof(T)), where, args);
        }

    }

    /// <summary>
    /// 表示该属性和数据表指定字段的绑定信息。
    /// </summary>
    [AttributeUsage(AttributeTargets.Property | AttributeTargets.Class | AttributeTargets.Struct)]
    public class DbBindAttribute : Attribute {

        /// <summary>
        /// 获取或设置当前属性对应数据库字段名。
        /// </summary>
        public string name;

        /// <summary>
        /// 初始化 <see cref="DbBindAttribute"/> 类的新实例。
        /// </summary>
        /// <param name="name">属性对应数据库字段名。</param>
        public DbBindAttribute(string name) {
            this.name = name;
        }

        ///// <summary>
        ///// 获取或设置当前属性的数据库类型。
        ///// </summary>
        //public SqlDbType dbType;

    }

    /// <summary>
    /// 表示该属性是数据库主键。
    /// </summary>
    [AttributeUsage(AttributeTargets.Property)]
    public sealed class DbPrimaryKeyAttribute : Attribute {

    }

}